/**
 * Labox有限公司项目InnovationServer
 * Labox版权所有 @copyright 2009
 * @javaclass WorldManager.java @author 丁宁 @time 2009-12-23
 */
package labox.innovation.gameserver.model;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import javolution.util.FastList;
import javolution.util.FastMap;
import labox.innovation.config.Config;
import labox.innovation.gameserver.GmListTable;
import labox.innovation.gameserver.datatables.CharNameTable;
import labox.innovation.gameserver.datatables.NpcTemplateTable;
import labox.innovation.gameserver.datatables.PlayerTemplateTable;
import labox.innovation.gameserver.model.actor.L2Character;
import labox.innovation.gameserver.model.actor.L2Playable;
import labox.innovation.gameserver.model.actor.instance.FuzePcInstance;
import labox.innovation.gameserver.model.actor.instance.L2PetInstance;
import labox.innovation.gameserver.templates.chars.NpcTemplate;
import labox.innovation.gameserver.util.Util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * @author Administrator
 * 
 */
public final class FuzeWorld {

	private static final Logger _log = LoggerFactory.getLogger(FuzeWorld.class.getName());

	/** L2ObjectHashMap(L2Object) containing all visible objects */
	private Map<Long, L2Object> _allObjects;

	private FastMap<Long, L2Character> _allCharacters;

	/** HashMap(String Player name, L2PcInstance) containing all the players in game */
	private FastMap<Long, FuzePcInstance> _allPlayers;

	/** List with the pets instances and their owner id */
	private Map<Long, L2PetInstance> _petsInstance;

	private ArrayList<Scene> scenes;

	public FuzeWorld() {
		_allObjects = new FastMap<Long, L2Object>(10000);
		_allCharacters = new FastMap<Long, L2Character>(5000);
		_allPlayers = new FastMap<Long, FuzePcInstance>();
		_petsInstance = new FastMap<Long, L2PetInstance>();
	}

	/**
	 * 装载游戏世界的配置
	 */
	public void load() {
		try {
			File f = new File(Config.DATAPACK_ROOT, "/Scene/world.xml");
			FileInputStream fin = new FileInputStream(f);
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(fin);
			Element root;
			root = document.getDocumentElement();
			if (root.getTagName().compareTo("World") != 0) {
				throw new Exception("Not a world file!");
			}
			fromXML(root);
			fin.close();
		} catch (Exception e) {
			_log.error("Wolrd file error exception:", Config.SERVER_ID, e);
		}

	}

	public final void fromXML(Element e) throws Exception {
		NodeList nl;
		int i = 0;

		// world header
		if (e.getTagName().compareTo("World") != 0) {
			throw new Exception("Expect World tag name");
		}

		//解析标记位
		//		t = (Element) e.getElementsByTagName("ActorFlags").item(0);
		//		nl = t.getElementsByTagName("Flag");
		//		flags = new ArrayList<ActorFlag>(nl.getLength());
		//		for (i = 0; i < nl.getLength(); i++) {
		//					flags.add(ActorFlag.fromXML((Element) nl.item(i)));
		//		}

		//解析所有角色模板
		PlayerTemplateTable.getInstance().fromXML();
		//解析所有的NPC模板
		NpcTemplateTable.getInstance().fromXML();
		//解析世界地图
		WorldMap.fromXMLFile();

		//解析所有的掉落模型
		try {

			File f = new File(Config.DATAPACK_ROOT, "/data/dropdata.xml");
			FileInputStream fin = new FileInputStream(f);
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(fin);
			Element root;
			root = document.getDocumentElement();
			if (root.getTagName().compareTo("Dropdata") != 0) {
				throw new Exception("Not a dropdata file!");
			}

			NodeList nll = root.getElementsByTagName("DropClass");
			Element el;
			for (i = -1; ++i < nll.getLength();) {
				el = (Element) nll.item(i);
				int id = Integer.parseInt(el.getAttribute("npc_templateid"));
				NpcTemplate template = NpcTemplateTable.getInstance().getTemplate(id);
				if (template == null) {
					_log.error("DropTemplate: Error loading Drop data.Not Found npc_templateid: " + id, Config.SERVER_ID);
					continue;
				}
				template.parseDropModel(el);
			}
			fin.close();
		} catch (Exception ex) {
			_log.error("Drop data: Error loading data. error row id:"+i, Config.SERVER_ID, ex);
		}

		try {
			File f = new File(Config.DATAPACK_ROOT, "/Scene/scene.xml");
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = builder.parse(f);
			document = builder.parse(f);
			Element root = document.getDocumentElement();
			if (root.getTagName().compareTo("Scenes") != 0) {
				throw new Exception("Not a Scenes file!");
			}

			//解析场景实例
			nl = root.getElementsByTagName("Scene");
			scenes = new ArrayList<Scene>(nl.getLength());
			for (i = -1; ++i < nl.getLength();) {
				Scene.fromXML((Element) nl.item(i), scenes);
			}
		} catch (Exception e2) {
			_log.error("Scene loading error. scene id:" + i, Config.SERVER_ID, e2);
		}

	}

	public Scene getScene(int id) {
		if (id >= scenes.size())
			return null;
		return scenes.get(id);
	}

	public static FuzeWorld getInstance() {
		return SingletonHolder._instance;
	}

	/**
	 * Return the L2Object object that belongs to an ID or null if no object found.<BR>
	 * <BR>
	 * 
	 * <B><U> Example of use </U> :</B><BR>
	 * <BR>
	 * <li>Client packets : Action, AttackRequest, RequestJoinParty, RequestJoinPledge...</li><BR>
	 * 
	 * @param oID
	 *            Identifier of the L2Object
	 */
	public L2Object findObject(long oID) {
		return _allObjects.get(oID);
	}

	/**
	 * Get the count of all visible objects in world.<br>
	 * <br>
	 * 
	 * @return count off all L2World objects
	 */
	public final int getAllVisibleObjectsCount() {
		return _allObjects.size();
	}

	public final Map<Long, L2Object> getAllObjects() {
		return _allObjects;
	}

	/**
	 * Return a table containing all GMs.<BR>
	 * <BR>
	 * 
	 */
	public FastList<FuzePcInstance> getAllGMs() {
		return GmListTable.getInstance().getAllGms(true);
	}

	public FastMap<Long, L2Character> getAllCharacters() {
		return _allCharacters;
	}

	public FastMap<Long, FuzePcInstance> getAllPlayers() {
		return _allPlayers;
	}

	/**
	 * Return how many players are online.<BR>
	 * <BR>
	 * 
	 * @return number of online players.
	 */
	public int getAllPlayersCount() {
		return _allPlayers.size();
	}

	/**
	 * Return the player instance corresponding to the given name.<BR>
	 * <BR>
	 * 
	 * @param name
	 *            Name of the player to get Instance
	 */
	public FuzePcInstance getPlayer(long oid) {
		return _allPlayers.get(oid);
	}

	/**
	 * Return the pet instance from the given ownerId.<BR>
	 * <BR>
	 * 
	 * @param ownerId
	 *            ID of the owner
	 */
	public L2PetInstance getPet(long ownerId) {
		return _petsInstance.get(ownerId);
	}

	/**
	 * Add the given pet instance from the given ownerId.<BR>
	 * <BR>
	 * 
	 * @param ownerId
	 *            ID of the owner
	 * @param pet
	 *            L2PetInstance of the pet
	 */
	private L2PetInstance addPet(long ownerId, L2PetInstance pet) {
		return _petsInstance.put(ownerId, pet);
	}

	/**
	 * Remove the given pet instance.<BR>
	 * <BR>
	 * 
	 * @param pet
	 *            the pet to remove
	 */
	private void removePet(L2PetInstance pet) {
		_petsInstance.remove(pet.getOwner().getObjectId());
	}

	public void enterWorld(L2Object object) {
		_allObjects.put(object.getObjectId(), object);
		if (object instanceof L2Character) {
			_allCharacters.put(object.getObjectId(), (L2Character) object);
			if (object instanceof FuzePcInstance) {
				FuzePcInstance cha = (FuzePcInstance) object;
				_allPlayers.put(cha.getObjectId(), cha);
			} else if (object instanceof L2PetInstance) {
				L2PetInstance pet = (L2PetInstance) object;
				FuzePcInstance owner = pet.getOwner();
				if (owner != null) {
					addPet(owner.getObjectId(), pet);
				}
			}
		}
	}

	public void exitWorld(L2Object object) {
		_allObjects.remove(object.getObjectId());
		if (object instanceof L2Character) {
			_allCharacters.remove(object.getObjectId());
			if (object instanceof FuzePcInstance) {
				_allPlayers.remove(object.getObjectId());
			} else if (object instanceof L2PetInstance) {
				removePet((L2PetInstance) object);
			}
		}
	}

	/**
	 * Add a L2Object in the world.<BR>
	 * <BR>
	 * 
	 */
	public void addVisibleObject(L2Object object, Scene scene) {
		scene.addVisibleObject(object);
	}

	/**
	 * Remove a L2Object from the world.<BR>
	 * <BR>
	 * 
	 */
	public void removeVisibleObject(L2Object object) {
		if (object == null)
			return;
		Scene scene = object.getPosition().getScene();
		if (scene != null) {
			scene.removeVisibleObject(object);
		}
	}

	/**
	 * Return all visible objects of the L2WorldRegion object's and of its surrounding L2WorldRegion.<BR>
	 * <BR>
	 * 
	 * <B><U> Concept</U> :</B><BR>
	 * <BR>
	 * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR>
	 * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area around a L2Object<BR>
	 * <BR>
	 * 
	 * <B><U> Example of use </U> :</B><BR>
	 * <BR>
	 * <li>Find Close Objects for L2Character</li><BR>
	 * 
	 * @param object
	 *            L2object that determine the current L2WorldRegion
	 * 
	 */
	public List<L2Object> getVisibleObjects(L2Object object) {
		if (object == null || !object.isVisible())
			return new ArrayList<L2Object>();
		SceneRegion[] reg = object.getKnownList().getKnownRegions();

		if (reg == null)
			return new ArrayList<L2Object>();

		// Create an FastList in order to contain all visible L2Object
		List<L2Object> result = new ArrayList<L2Object>();

		// Go through the FastList of region
		for (SceneRegion regi : reg) {
			// Go through visible objects of the selected region
			Collection<L2Object> vObj = regi.getVisibleObjects().values();

			for (L2Object _object : vObj) {
				if (_object == object)
					continue; // skip our own character
				if (!_object.isVisible())
					continue; // skip dying objects
				result.add(_object);
			}
		}

		return result;
	}

	//	public FuzePcInstance getPlayer(String name)
	//	{
	//		return getPlayer(CharNameTable.getInstance().getIdByName(name));
	//	}
	/**
	 * Return all visible objects of the L2WorldRegions in the circular area (radius) centered on the object.<BR>
	 * <BR>
	 * 
	 * <B><U> Concept</U> :</B><BR>
	 * <BR>
	 * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR>
	 * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area around a L2Object<BR>
	 * <BR>
	 * 
	 * <B><U> Example of use </U> :</B><BR>
	 * <BR>
	 * <li>Define the aggrolist of monster</li> <li>Define visible objects of a L2Object</li> <li>Skill : Confusion...</li><BR>
	 * 
	 * @param object
	 *            L2object that determine the center of the circular area
	 * @param radius
	 *            Radius of the circular area
	 * 
	 */
	public List<L2Object> getVisibleObjects(L2Object object, int radius) {
		if (object == null || !object.isVisible())
			return new ArrayList<L2Object>();

		// Create an FastList in order to contain all visible L2Object
		List<L2Object> result = new ArrayList<L2Object>();

		SceneRegion[] reg = object.getKnownList().getKnownRegions();

		if (reg == null)
			return new ArrayList<L2Object>();
		// Go through the FastList of region
		for (SceneRegion regi : reg) {
			// Go through visible objects of the selected region
			Collection<L2Object> vObj = regi.getVisibleObjects().values();
			for (L2Object _object : vObj) {
				if (_object == object)
					continue; // skip our own character
				if (!_object.isVisible())
					continue; // skip dying objects
				if (Util.FastDistance2D(object.getX(), object.getY(), _object.getX(), _object.getY()) < radius)
					result.add(_object);
			}

		}

		return result;
	}

	/**
	 * Return all visible players of the L2WorldRegion object's and of its surrounding L2WorldRegion.<BR>
	 * <BR>
	 * 
	 * <B><U> Concept</U> :</B><BR>
	 * <BR>
	 * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR>
	 * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area around a L2Object<BR>
	 * <BR>
	 * 
	 * <B><U> Example of use </U> :</B><BR>
	 * <BR>
	 * <li>Find Close Objects for L2Character</li><BR>
	 * 
	 * @param object
	 *            L2object that determine the current L2WorldRegion
	 * 
	 */
	public List<L2Playable> getVisiblePlayable(L2Object object) {
		if (object == null || !object.isVisible())
			return new ArrayList<L2Playable>();

		SceneRegion[] reg = object.getKnownList().getKnownRegions();

		if (reg == null)
			return new ArrayList<L2Playable>();

		// Create an FastList in order to contain all visible L2Object
		List<L2Playable> result = new ArrayList<L2Playable>();

		// Go through the FastList of region
		for (SceneRegion regi : reg) {
			for (L2Playable _object : regi.getVisiblePlayable().values()) {
				if (_object == object)
					continue; // skip our own character
				if (!_object.isVisible()) // GM invisible is different than this...
					continue; // skip dying objects
				result.add(_object);
			}

		}

		return result;
	}

	/**
	 * 删除所有世界上的可见NPC的出生点.
	 */
	public void deleteVisibleNpcSpawns() {
		_log.info("Deleting all visible NPC's.", Config.SERVER_ID);
		for (int i = -1; ++i < scenes.size();) {
			scenes.get(i).deleteVisibleNpcSpawns();
		}
		_log.info("All visible NPC's deleted.", Config.SERVER_ID);
	}

	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder {
		protected static final FuzeWorld _instance = new FuzeWorld();
	}

}
